<?php
require_once('../config.php');
require_once('../../taxamatch/api/config.php');
require_once('../../taxamatch/api/classes/class.taxamatch.php');
require_once('../../taxamatch/api/classes/class.nearmatch.php');
require_once('../../taxamatch/api/classes/class.gni_splitter.php');

class Database {

  const BATCH_SIZE = 100;
  const TMQUE = 'tm_que';
  const TMERR = 'tm_err_que';

  private static $db_instance;

  public function __construct() {
    global $db;
    $this->cursor = new mysqli($db['host'], $db['user'], $db['pass'], $db['db']);
    $this->cursor->set_charset($db['enc']);
    $this->shared_cursor = new mysqli($db['host_shared'], $db['user_shared'], $db['pass_shared'], $db['db_shared']);
    $this->shared_cursor->set_charset($db['enc']);
    $this->starling_host = $db['starling_host'];
  }
  
  public static function instance() {
    if (!self::$db_instance) {
      self::$db_instance = new Database();
    }
    return self::$db_instance;
  }
}

class GniMatcher {
	public function __construct() {
	  $db_inst = Database::instance();
    $this->db = $db_inst->cursor;
    $this->tm = new Taxamatch();
    $this->nm = new NearMatch();
    $this->semantics = $this->get_semantics();
    $this->cache_strings_match = array();
    $this->cache_species_match = array();
    $this->cache_parsed = array();
  }

  public function match_genera($genus1, $genus1_id) {
    $result = array();
    $genera = $this->get_genera($genus1); 
    while ($record = $genera->fetch_object()){
      $genus2 = $record->normalized;
      $genus2_id = $record->id;
      $match = $this->tm->match_genera($genus1, $genus2);
      if($match['match']) $result[$genus2_id] = array('normalized' => $genus2, 'match' => $match);
    }
    //print_r($result);
    $json_result = $this->db->escape_string(json_encode($result)); //TODO find out if it is the right way of doing it
    $this->db->query("update genus_words set matched_data = '$json_result' where id = $genus1_id");
    
    return $result;
  }
  
  public function match_names($species1, $genera_match, $canonical1_id) {
    $genera_ids = array_keys($genera_match);
    $genera_ids = implode(",", $genera_ids);
    
    if ($genera_ids == '') return array();
    
    $canonical_ids = array();
    $species = $this->get_species($genera_ids, $species1, $canonical1_id);
    while($record = $species->fetch_object()){
      $canonical2_id = $record->id;
      $genus2_id = $record->word1_id;
      $species2 = $record->word2;
      $species_match = $this->match_species($species1, $species2);
      if ($species_match['match']) {
        $genus_match = $genera_match[$genus2_id]['match'];
        $binomial_match = $this->tm->match_matches($genus_match, $species_match);
        if ($binomial_match['match']) {  
          $canonical_ids[] = array($canonical2_id, $binomial_match['edit_distance']);
        }
      }
    }
    
    return $canonical_ids;
  }
  
  public function match_name_strings($canonical1, $canonicals) {
    $names1 = $this->get_names($canonical1);
    $matchers = $this->compare_authors($names1, $names1, 0);
    foreach($canonicals as $result) {
      list($canonical, $edit_distance) = $result;
      
      $names2 = $this->get_names($canonical);
      $cmp = $this->compare_authors($names1, $names2, $edit_distance);
      $matchers = array_merge($matchers, $cmp);
    }
    return $matchers;
  }
  

  //Private methods

  private function get_authors($id) {
    if (isset($this->cache_parsed[$id])) {
      $preparsed = $this->cache_parsed[$id];
    } else {
      $year_id = $this->semantics['year'];
      $query = "select nw.word from name_words nw join name_word_semantics nws on nw.id = nws.name_word_id where nws.name_string_id = $id and semantic_meaning_id = $year_id";
      $years = $this->db->query($query);
      $author_word_id = $this->semantics['author_word'];
      $query = "select nw.word from name_words nw join name_word_semantics nws on nw.id = nws.name_word_id where nws.name_string_id = $id and semantic_meaning_id = $author_word_id";
      $authors = $this->db->query($query); 
      $all_years = array();
      while($years && $row=$years->fetch_assoc()) {
        $year = $row['word'];
        if ($year > 1700) $all_years[] = $year;
      }
      
      $all_authors = array();
      while($authors && $row=$authors->fetch_assoc()) {
        $all_authors[] = $row['word'];
      }
      $preparsed = array('all_authors' => $all_authors, 'all_years' => $all_years);
      $this->cache_parsed[$id] = $preparsed;
    }
    return $preparsed;
  }

  private function compare_authors($names1, $names2, $edit_distance) { 
    $matchers = array();
    foreach($names1 as $name_ary) {
      $id1 = $name_ary[0];
      $name1 = $name_ary[1];
      $auth1 = $this->get_authors($id1);
      foreach($names2 as $name_ary2) {
        $id2 = $name_ary2[0];
        $name2 = $name_ary2[1];
        if ($id1 != $id2 && !isset($this->cache_strings_match["$id1|$id2"]) && count($auth1['all_authors']) != 0) {
          $auth2 = $this->get_authors($id2);
          $match = 0;
          if (count($auth2['all_authors']) > 0) $match = $this->tm->match_authors($auth1, $auth2, false);
          $this->cache_strings_match["$id1|$id2"] = 1;
          $this->cache_strings_match["$id2|$id1"] = 1;
          if ($match && $match > 50) {
            $matchers[] = array($id1, $id2, $name1, $name2, $edit_distance, $match);
            $matchers[] = array($id2, $id1, $name2, $name1, $edit_distance, $match);
          }
        }
      }
    }
    return $matchers;
  }
  
  private function get_names($canonical_id) {
    $names = array();
    $query = "select ns.id, ns.name from name_strings ns join extended_canonical_forms ecf on ecf.id = ns.canonical_form_id where canonical_form_id = $canonical_id and ns.is_canonical_form = 0";
    $res = $this->db->query($query);
    while($record = $res->fetch_object()){
      $id = $record->id;
      $name = $record->name;
      $names[] = array($id, $name);
    }    
    return $names;
  }
  
  private function match_species($species1, $species2) {
    $species = array($species1, $species2);
    sort($species, SORT_STRING);
    $species_key = implode("|", $species);
    if (isset($this->cache_species_match[$species_key])) return $this->cache_species_match[$species_key];
    $match = $this->tm->match_species_epithets($species1, $species2);
    $this->cache_species_match[$species_key] = $match;
    return $match;
  }

  private function get_species($genera_ids, $species1, $canonical1_id) {
    $length = $this->length_min_max($species1);
    $first_letter = substr($species1, 0, 1);
    $min = $length['min'];
    $max = $length['max'];
    $query  = "select id, word1_id, word2 from extended_canonical_forms where word1_id in ($genera_ids) and number_of_words = 2 and id != $canonical1_id and word2 like '$first_letter%' and word2_length between $min and $max";
    return $this->db->query($query);
  }
  
//  private function tm_prepare($word) {
//   $return = array();
//   $return['normalized'] = $word;
//   $return['phonetized'] = $this->nm->near_match($word);
//  }
//
  private function get_genera($genus) {
    $length = $this->length_min_max($genus);
    $first_letter = substr($genus, 0,1);
    $min = $length['min'];
    $max = $length['max'];
    $query = "select id, normalized from genus_words where first_letter = '$first_letter' and length between $min and $max";
    return $this->db->query($query);
  }

  private function length_min_max($word) {
    $length = strlen($word);
    $lengths = array();
    $delta =  round($length/5.0);
    $lengths['min'] = $length - $delta; 
    $lengths['max'] = $length + $delta; 
    return $lengths;
  }
  
  private function get_semantics(){
    $res = $this->db->query("select id, name from semantic_meanings");
    $return = array();
    while($record = $res->fetch_object()){
      $return[$record->name] = $record->id;
    }
    return $return;
  }
}
